/**
 * Copyright 2018 人人开源 http://www.renren.io
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package com.freeter.common.aspect;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.PropertyFilter;
import com.freeter.common.annotation.SysLog;
import com.freeter.common.baseapi.IBaseApi;
import com.freeter.common.baseapi.vo.SysLogVo;
import com.freeter.common.utils.HttpContextUtils;
import com.freeter.common.utils.IPUtils;

import com.freeter.common.utils.R;
import com.freeter.common.utils.SpringContextUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.Date;

/**
 * 系统日志，切面处理类
 *
 * @author Mark sunlightcs@gmail.com
 * @since 1.3.0 2017-03-08
 */
@Aspect
@Component
public class SysLogAspect {
	@Autowired
	private IBaseApi baseApi;
	
	@Pointcut("@annotation(com.freeter.common.annotation.SysLog)")
	public void logPointCut() { 
		
	}

	@Around("logPointCut()")
	public Object around(ProceedingJoinPoint point) throws Throwable {
		long beginTime = System.currentTimeMillis();
		//执行方法
		Object result = point.proceed();
		//执行时长(毫秒)
		long time = System.currentTimeMillis() - beginTime;

		//保存日志
		saveSysLog(point, time, result);

		return result;
	}

	private void saveSysLog(ProceedingJoinPoint joinPoint, long time,Object result) {
		MethodSignature signature = (MethodSignature) joinPoint.getSignature();
		Method method = signature.getMethod();

		SysLogVo sysLog = new SysLogVo();
		SysLog syslogAnnotation = method.getAnnotation(SysLog.class);
		if(syslogAnnotation != null){
			//注解上的描述
			sysLog.setOperation(syslogAnnotation.value());
			sysLog.setGroupName(syslogAnnotation.groupName());
		}

		//请求的方法名
		String className = joinPoint.getTarget().getClass().getName();
		String methodName = signature.getName();
		sysLog.setMethod(className + "." + methodName + "()");

		//获取request
		HttpServletRequest request = SpringContextUtils.getHttpServletRequest();

		//请求的参数
		String requestParams = getReqestParams(request,joinPoint,syslogAnnotation,sysLog);
		sysLog.setParams(requestParams);

		//设置IP地址
		sysLog.setIp(IPUtils.getIpAddr(request));
//		try {
//			SysUserEntity loginUser = SecurityUtils.getSubject().getPrincipal();
//		}catch (Exception e){
//
//		}

		//用户名
		String loginUsrName = (String) request.getAttribute("LOGIN_USER");
		if(StringUtils.isNotEmpty(loginUsrName)) {
			sysLog.setUsername(loginUsrName);
		}
		sysLog.setTime(time);
		sysLog.setCreateDate(new Date());

		String idFieldName = syslogAnnotation.bizidFieldName();
		String updateIdFieldName = syslogAnnotation.updateBizidFieldName();
		if(result instanceof R && StringUtils.isNotEmpty(idFieldName) && StringUtils.isEmpty(sysLog.getBizId())){
			R ret = (R)result;
			if(ret.isOk() == true){
				Object o = ret.get("data");
				Object fieldValueByName = this.getFieldValueByName(idFieldName, o);
				if(null == fieldValueByName) {
					fieldValueByName = this.getFieldValueByName(updateIdFieldName, o);
				}
				if(null != fieldValueByName) {
					sysLog.setBizId(fieldValueByName.toString());
				}
			}
		}

		//保存系统日志
		baseApi.insert(sysLog);
	}

	private Object getFieldValueByName(String fieldName, Object o) {
		String[] fieldNames = fieldName.split("/");
		Object resultVal = null;
		Object ParaObj  = o;
		for(String fName : fieldNames) {
			try {
				String firstLetter = fName.substring(0, 1).toUpperCase();
				String getter = "get" + firstLetter + fName.substring(1);
				Method method = ParaObj.getClass().getMethod(getter, new Class[]{});
				resultVal = method.invoke(ParaObj, new Object[]{});
				ParaObj = resultVal;
			} catch (Exception e) {

			}
		}
		return resultVal;
	}

	/**
	 * @Description: 获取请求参数
	 * @author: scott
	 * @date: 2020/4/16 0:10
	 * @param request:  request
	 * @param joinPoint:  joinPoint
	 * @Return: java.lang.String
	 */
	private String getReqestParams(HttpServletRequest request, JoinPoint joinPoint,SysLog sysLogAnnotation,SysLogVo sysLog) {
		String httpMethod = request.getMethod();
		String params = "";
		if ("POST".equals(httpMethod) || "PUT".equals(httpMethod) || "PATCH".equals(httpMethod)) {
			Object[] paramsArray = joinPoint.getArgs();
			// java.lang.IllegalStateException: It is illegal to call this method if the current request is not in asynchronous mode (i.e. isAsyncStarted() returns false)
			//  https://my.oschina.net/mengzhang6/blog/2395893

			//业务数据id和字段名
			int idIdx = sysLogAnnotation.bizIdParamIdx();
			String idFieldName = sysLogAnnotation.bizidFieldName();
			String updateIdFieldName = sysLogAnnotation.updateBizidFieldName();

			Object[] arguments  = new Object[paramsArray.length];
			for (int i = 0; i < paramsArray.length; i++) {
				if (paramsArray[i] instanceof BindingResult || paramsArray[i] instanceof ServletRequest || paramsArray[i] instanceof ServletResponse || paramsArray[i] instanceof MultipartFile ) {
					//ServletRequest不能序列化，从入参里排除，否则报异常：java.lang.IllegalStateException: It is illegal to call this method if the current request is not in asynchronous mode (i.e. isAsyncStarted() returns false)
					//ServletResponse不能序列化 从入参里排除，否则报异常：java.lang.IllegalStateException: getOutputStream() has already been called for this response
					continue;
				}
				arguments[i] = paramsArray[i];
				if(StringUtils.isNotEmpty(idFieldName)){
					if(i == idIdx){
						//JSONObject o = (JSONObject)JSONObject.toJSON(paramsArray[i]);
						//sysLog.setBizId(String.valueOf(o.get(idFieldName)));
						Object fieldValueByName = this.getFieldValueByName(idFieldName, paramsArray[i]);
						if(null == fieldValueByName) {
							fieldValueByName = this.getFieldValueByName(updateIdFieldName, paramsArray[i]);
						}
						if(null != fieldValueByName){
							sysLog.setBizId(fieldValueByName.toString());
						}
					}
				}
			}
			//update-begin-author:taoyan date:20200724 for:日志数据太长的直接过滤掉
			PropertyFilter profilter = new PropertyFilter() {
				@Override
				public boolean apply(Object o, String name, Object value) {
					if(value!=null && value.toString().length()>1000){
						return false;
					}
					if(name.equals("password")){
						return false;
					}
					return true;
				}
			};
			params = JSONObject.toJSONString(arguments, profilter);
			//update-end-author:taoyan date:20200724 for:日志数据太长的直接过滤掉
		} else {
			MethodSignature signature = (MethodSignature) joinPoint.getSignature();
			Method method = signature.getMethod();
			// 请求的方法参数值
			Object[] args = joinPoint.getArgs();
			// 请求的方法参数名称
			LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
			String[] paramNames = u.getParameterNames(method);
			if (args != null && paramNames != null) {
				int idIdx = sysLogAnnotation.bizIdParamIdx();
				for (int i = 0; i < args.length; i++) {
					if(i == idIdx){
						sysLog.setBizId(args[i].toString());
					}
					params += "  " + paramNames[i] + ": " + args[i];
				}
			}
		}
		return params;
	}
}
